#include <stdio.h>
#include <string.h>
#include <syscall-nr.h>
#include "pagedir.h"
#include "process.h"
#include "vm/frame.h"
#include "filesys/file.h"
#include "filesys/directory.h"
#include "threads/vaddr.h"
#include "threads/malloc.h"
#include "threads/thread.h"
#include "filesys/filesys.h"
#include "lib/user/syscall.h"
#include "devices/shutdown.h"
#include "userprog/syscall.h"
#include "threads/interrupt.h"

static void syscall_handler (struct intr_frame *);
static bool checkforvalidaddr (void *);
static void inform_children(struct thread *);

/* Prototypes for all system call handlers */
static void halt_handler (void);
static pid_t exec_handler (const char *);
static int wait_handler (pid_t);
static bool create_handler (const char *, unsigned);
static bool remove_handler (const char *);
static int open_handler (const char *);
static int filesize_handler (int);
static int read_handler (int, void *, unsigned);
static int write_handler (int, const void *, unsigned);
static void seek_handler (int, unsigned);
static unsigned tell_handler (int);
static void close_handler (int);
static bool chdir_handler (const char *);
static bool mkdir_handler (const char *);
static bool readdir_handler (int, char *);
static bool isdir_handler (int);
static int inumber_handler (int);
static mapid_t mmap_handler (int, void *);
static void munmap_handler (mapid_t);

/* static char *prog_name; */

void
syscall_init (void) 
{
  intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
}

static void
syscall_handler (struct intr_frame *f UNUSED) 
{
  /* Address validity check */
  if (!f || !checkforvalidaddr (f->esp))
    exit_handler (-1);

  thread_current ()->esp_user = f->esp;
  thread_current ()->system_call = true;
  int syscall_num = *((int *) f->esp);  /* System call number */
  void *buffer;
  int i_arg;
  pid_t p_arg;
  char *s_arg;
  unsigned u_arg;

  switch (syscall_num)
  {
    case SYS_HALT:
      halt_handler ();
      break;

    case SYS_EXIT:
      /* Address validity check */
      if (!checkforvalidaddr (f->esp + 4))
        exit_handler (-1); 
      
      /* Argument 1: Status */
      i_arg = *((int *) (f->esp + 4));
      exit_handler (i_arg);
      break;

    case SYS_EXEC:
      /* Address validity check */
      if (!checkforvalidaddr (f->esp + 4)
          || (!checkforvalidaddr ((void *) *((uint32_t *) (f->esp + 4)))))
	exit_handler (-1);
      
      /* Argument 1: Executable's name */
      s_arg = (char *) (* (uint32_t *) (f->esp + 4));
      f->eax = (int) exec_handler (s_arg);
      break;

    case SYS_WAIT:
      /* Address validity check */
      if (!checkforvalidaddr (f->esp + 4))
	exit_handler (-1);
      
      /* Argument 1: Child process PID */
      p_arg = *((int *) (f->esp + 4)); 
      f->eax = wait_handler (p_arg);
      break;

    case SYS_CREATE:
      /* Address validity check */
      if (!checkforvalidaddr (f->esp + 8)
          || !checkforvalidaddr (f->esp + 16)
	  || !checkforvalidaddr ((void *) *((uint32_t *) (f->esp + 16))))
	exit_handler (-1);
      
      /* Argument 1: File name */
      s_arg = (char *) *((uint32_t *) (f->esp + 16));
      /* Argument 2: Initial size */
      u_arg = *((unsigned *) (f->esp + 8));
      f->eax = create_handler (s_arg, u_arg);
      break;
    
    case SYS_REMOVE:
      /* Address validity check */
      if (!checkforvalidaddr (f->esp + 12)
          || !checkforvalidaddr ((void *) *((uint32_t *) (f->esp + 12))))
	exit_handler (-1);

      /* Argument 1: File name */
      s_arg = (char *) *((uint32_t *) (f->esp + 12));
      f->eax = remove_handler (s_arg);
      break;

    case SYS_OPEN:
      /* Address validity check */
      if (!checkforvalidaddr (f->esp + 4)
          || !checkforvalidaddr ((void *) *((uint32_t *) (f->esp + 4))))
        exit_handler (-1);

      /* Argument 1: File name */
      s_arg = (char *) *((uint32_t *) (f->esp + 4));
      f->eax = open_handler (s_arg);
      break;
   
    case SYS_FILESIZE:
      /* Address validity check */
      if (!checkforvalidaddr (f->esp + 4))
        exit_handler (-1);

      /* Argument 1: File descriptor */
      i_arg = *((int *) (f->esp + 4));
      f->eax = filesize_handler (i_arg);
      break;
    
    case SYS_READ:
      /* Address validity check */
      if (!checkforvalidaddr (f->esp + 4)
          || !checkforvalidaddr (f->esp + 8)
          || !checkforvalidaddr (f->esp + 24)
          || !checkforvalidaddr ((void *) *((uint32_t *) (f->esp + 24))))
        exit_handler (-1);
	
      /* Argument 1: File descriptor */
      i_arg = *((int *) (f->esp + 8));
      /* Argument 2: Buffer */
      buffer = (char *) (*((uint32_t *) (f->esp + 24)));
      /* Argument 3: Size of buffer */
      u_arg = *((unsigned *) (f->esp + 4));
      f->eax = read_handler (i_arg, buffer, u_arg);
      break;

    case SYS_WRITE:
      /* Address validity check */
      if (!checkforvalidaddr (f->esp + 4)
          || !checkforvalidaddr (f->esp + 8)
          || !checkforvalidaddr (f->esp + 24)
	  || !checkforvalidaddr ((void *) *((uint32_t *) (f->esp + 24))))
        exit_handler (-1);

      /* Argument 1: File descriptor */
      i_arg = *((int *) (f->esp + 8));
      /* Argument 2: Buffer */
      buffer = (char *) (*((uint32_t *) (f->esp + 24)));
      /* Argument 3: Size of buffer */
      u_arg = *((unsigned *) (f->esp + 4));
      f->eax = write_handler (i_arg, buffer, u_arg);
      break;
    
    case SYS_SEEK:
      /* Address validity check */  
      if (!checkforvalidaddr (f->esp + 8)
          || !checkforvalidaddr (f->esp + 16))
        exit_handler (-1);
      
      /* Argument 1: File descriptor */
      i_arg = *((int *) (f->esp + 16));
      /* Argument 2: Position with the file */
      u_arg = *((unsigned *) (f->esp + 8));
      seek_handler (i_arg, u_arg);
      break;

    case SYS_TELL:
      /* Address validity check */
      if (!checkforvalidaddr (f->esp + 4))
        exit_handler (-1);

      /* Argument 1: File descriptor */
      i_arg = *((int *) (f->esp + 4));
      f->eax = tell_handler (i_arg);
      break;
   
    case SYS_CLOSE:
      /* Address validity check */
      if (!checkforvalidaddr (f->esp + 4))
        exit_handler (-1);

      /* Argument 1: File descriptor */
      i_arg = *((int *) (f->esp + 4));
      close_handler (i_arg);
      break;

    case SYS_MMAP:
      /* Address validity check */
      if (!checkforvalidaddr (f->esp + 8)
          || !checkforvalidaddr ((void *) *((uint32_t *) (f->esp + 8)))
          || !checkforvalidaddr (f->esp + 16))
        exit_handler (-1);

      i_arg = *((int *) (f->esp + 16));
      buffer = (void *) *((uint32_t *) (f->esp + 8));
      f->eax = mmap_handler (i_arg, buffer);
      break;

    case SYS_MUNMAP:
      /* Address validity check */
      if (!checkforvalidaddr (f->esp + 4))
        exit_handler (-1);

      i_arg = *((int *) (f->esp + 4));
      munmap_handler (i_arg);
      break;

    case SYS_CHDIR:
       if (!checkforvalidaddr (f->esp + 4)
          || !checkforvalidaddr ((void *) *((uint32_t *) (f->esp + 4))))
	  exit_handler (-1);

      s_arg = (char *) *((uint32_t *) (f->esp + 4));
      f->eax = chdir_handler (s_arg);
      break;

    case SYS_MKDIR:
       if (!checkforvalidaddr (f->esp + 4)
          || !checkforvalidaddr ((void *) *((uint32_t *) (f->esp + 4))))
	  exit_handler (-1);

      s_arg = (char *) *((uint32_t *) (f->esp + 4));
      f->eax = mkdir_handler (s_arg);
      break;

    case SYS_READDIR:
       if (!checkforvalidaddr (f->esp + 4)
          || !checkforvalidaddr ((void *) *((uint32_t *) (f->esp + 4)))
          || !checkforvalidaddr (f->esp + 8))
	  exit_handler (-1);

      s_arg = (char *) *((uint32_t *) (f->esp + 4));
      i_arg = *((int *) (f->esp + 8));
      f->eax = readdir_handler (i_arg, s_arg);
      break;

    case SYS_ISDIR:
      if (!(checkforvalidaddr (f->esp + 4)))
	  exit_handler (-1);

      i_arg = *((int *) (f->esp + 4));
      f->eax = isdir_handler (i_arg);
      break;

    case SYS_INUMBER:
      if (!(checkforvalidaddr (f->esp + 4)))
	  exit_handler (-1);

      i_arg = *((int *) (f->esp + 4));
      f->eax = inumber_handler (i_arg);
      break;

    default:
      printf ("INVALID SYSTEM CALL.\n");
      thread_exit ();
      break;
  }

  thread_current ()->system_call = false;
}

/* Handler for HALT system call */
static void 
halt_handler (void)
{
  shutdown_configure (SHUTDOWN_POWER_OFF);
  shutdown_power_off ();
}

/* Handler for EXIT system call */
void
exit_handler (int status)
{
  struct thread *t = thread_current ();
  t->exit_status = status;
  set_exit_status (status, t);
  inform_children (t);

  if (t->file)
    file_close (t->file);
  
  if (t->parent)
  {
    /* If parent was waiting for this child to exit */
    if (t->parent->waiting_for_child_tid == t->tid)
    {
      t->parent->waiting_for_child_exit_status = status;
      sema_up (&t->parent->wait_sema);
    }
  }

  /* Deallocate associated memory resources */
  free_children ();

  /* Unmap all memory mapped pages back to their files */
  mapid_t index = 0;
  while (++index <= t->mapid_count)
    munmap_handler (index);

  /* Print exit status and leave */
  printf ("%s: exit(%d)\n", t->name, status);
  thread_exit ();
}

/* Handler for EXEC system call */
static pid_t
exec_handler (const char *cmd_line)
{
  pid_t pid = (pid_t) process_execute (cmd_line);
  if (pid == -1)
    return -1;  /* Execution failure */
  
  struct thread *t = thread_current ();
  sema_down (&t->load_sema); 

  if (t->load_success)
    return pid;
  else
    return -1;
}

/* Handler for WAIT system call */
static int 
wait_handler (pid_t pid)
{
  /* Call helper function */
  return process_wait (pid);
}

/* Handler for CREATE system call */
static bool
create_handler (const char *file, unsigned initial_size)
{
  bool result;

 /* Additional address validity check */
  if (!file
      || !checkforvalidaddr ((void *) file))
    exit_handler (-1);

  lock_acquire (&global_filesys_lock);
  result = filesys_create (file, initial_size); 
  lock_release (&global_filesys_lock);
  
  return result;
}

/* Handler for REMOVE system call */
static bool 
remove_handler (const char *file)
{
  bool result;

  /* Additional address validity check */
  if (!checkforvalidaddr ((void *) file))
    exit_handler (-1);
  
  lock_acquire (&global_filesys_lock);
  result = filesys_remove (file);
  lock_release (&global_filesys_lock);
  
  return result;
}

/* Handler for OPEN system call */
static int
open_handler (const char *file)
{
  /* Additional address validity check */
  if (!checkforvalidaddr ((void *) file) 
      || !checkforvalidaddr ((void *) (file + strlen (file))))
    exit_handler (-1);
  
  lock_acquire (&global_filesys_lock);
  struct dir *path;
  char *temp1, *temp2, *rest;
  struct inode *inode;
  struct thread *cur = thread_current ();
  bool success;
 
  if (strlen(file) == 0)
      return -1;
 
  if (file[0] == '/')
      path = dir_open_root ();
  else
      path = dir_reopen (cur->working_dir);

  temp1 = strtok_r (file, "/", &rest);
  for (temp2 = strtok_r (NULL, "/", &rest); temp2 != NULL; temp2 = strtok_r (NULL, "/", &rest))
  {
    struct inode *inode = NULL;
    success = dir_lookup (path, temp1, &inode);
    if (!success)
    {
      return false;
    }
    dir_close (path);
    path = dir_open (inode);   
    temp1 = temp2;
  }

  if (temp1 == NULL)
      inode = inode_open (ROOT_DIR_SECTOR);
  else if (!dir_lookup (path, temp1, &inode))
      return -1;
      
  dir_close (path);
  
  /* Create file information structure and update its attributes */
  struct file_info *file_info = malloc (sizeof (struct file_info));
  if (inode_get_isdir (inode))
  {
    file_info->file = NULL;
    file_info->dir = dir_open (inode);
  }
  else
  {
    file_info->file = filesys_open (temp1);
    file_info->posn = 0;
    file_info->dir = NULL;
  }
  /* Add the opened file to the current thread's list */
  /* of open files */
  lock_release (&global_filesys_lock);
  return add_in_files (cur, file_info);   	
}

/* Handler for FILESIZE system call */
static int
filesize_handler (int fd)
{
  int result;  

  struct thread *t = thread_current ();
  if (!t->files[fd])
    return -1;
  
  lock_acquire (&global_filesys_lock);
  result = file_length (t->files[fd]->file);
  lock_release (&global_filesys_lock);

  return result;
}

/* Handler for READ system call */
static int
read_handler (int fd, void *buffer, unsigned size)
{
  int result = 0;
  struct file_info *file_info;

  /* If STDIN, use input_getc() */  
  if (fd == STDIN_FILENO)
  {
    int i;
    for (i = 0; i < size; i++)
    {
      *(uint8_t *) buffer = input_getc();
      buffer++;
      result++;
    }

    return result;
  }
    
  /* Check for invalid file descriptors */
  if (fd < 0 || fd == STDOUT_FILENO || fd == 2 || fd >= 32)
    return -1;

  file_info = thread_current ()->files[fd];
  if (!file_info)
    return -1;
  
  lock_acquire (&global_filesys_lock);
  result = file_read (file_info->file, buffer, size);
  lock_release (&global_filesys_lock);

  return result;
}

/* Handler for WRITE system call */
static int
write_handler (int fd, const void *buffer, unsigned size)
{
  int result = 0;

  if (fd == STDOUT_FILENO)
  {
    /* Write to console */
    putbuf (buffer, size);
    return size;
  }

  /* check for invalid file descriptor */
  if (fd == STDIN_FILENO || fd == 2 || fd < 0 || fd >= 32)
     return 0;

  /* Write to file */
  struct file_info *file_info = thread_current ()->files[fd];
  if (!file_info)
    return 0;
    
  lock_acquire (&global_filesys_lock);
  result = file_write (file_info->file, buffer, size);
  lock_release (&global_filesys_lock);
   
  return result;
}

/* Handler for SEEK system call */
static void
seek_handler (int fd, unsigned position)
{
  /* Check for valid file descriptors */
  if (fd < 3 || fd > 31)
    exit_handler (-1);
  
  struct file_info *file_info = thread_current ()->files[fd];
  if (file_info == NULL)
    exit_handler (-1);

  lock_acquire (&global_filesys_lock);
  file_seek (file_info->file, position);
  lock_release (&global_filesys_lock);
}

/* Handler for TELL system call */
static unsigned
tell_handler (int fd)
{
  unsigned result;

  /* Check for valid file descriptors */
  if (fd < 3 || fd > 31)
    exit_handler (-1);
  
  struct file_info *file_info = thread_current ()->files[fd];
  if (file_info == NULL)
    exit_handler (-1);

  lock_acquire (&global_filesys_lock);
  result = file_tell (file_info->file);
  lock_release (&global_filesys_lock);

  return result;
}

/* Handler for CLOSE system call */
static void
close_handler (int fd)
{ 
  struct file_info *file_info = thread_current ()->files[fd];
  
  if (file_info)
  {
    lock_acquire (&global_filesys_lock);
    file_close (file_info->file);
    lock_release (&global_filesys_lock);
    
    thread_current ()->files[fd] = NULL;
    free_file (fd);  /* Deallocate file_info memory */
  }
}

/* change working directory to DIR */
static bool 
chdir_handler (const char *dir)
{
  lock_acquire (&global_filesys_lock);
  bool success = true;
  char *first, *rest;
  struct inode * inode = NULL;
  struct thread *cur = thread_current ();

  if (dir[0] == '/')
  {
    dir_close (cur->working_dir);
    cur->working_dir = dir_open_root ();
  }
  
  for (first = strtok_r (dir, "/", &rest); first != NULL; first = strtok_r (NULL, "/", &rest))
  {
    if (!dir_lookup (cur->working_dir, first, &inode))
    {
      success = false;
      break;
    }
    dir_close (cur->working_dir);
    cur->working_dir = dir_open (inode);      
  }
  
  lock_release (&global_filesys_lock);
  return success;
}

/* Create a new directory if DIR is valid */
static bool 
mkdir_handler (const char *dir)
{
  struct thread *cur = thread_current ();
  struct dir *path;
  char *temp1, *temp2, *rest;
  bool success = true;

  if (strlen (dir) == 0)
      return false;

  if (dir[0] == '/')
      path = dir_open_root ();
  else
      path = dir_reopen (cur->working_dir);

  temp1 = strtok_r (dir, "/", &rest);
  
  for (temp2 = strtok_r (NULL, "/", &rest); temp2 != NULL; temp2 = strtok_r (NULL, "/", &rest))
  {
    struct inode *inode = NULL;
    success = dir_lookup (path, temp1, &inode);
    if (!success)
    {
      return false;
    }
    dir_close (path);
    path = dir_open (inode);   
    temp1 = temp2;
  }

  if (temp1 == NULL)  
      return false;
  
  block_sector_t bst = 0;
  success = (path != NULL
                     && free_map_allocate (1, &bst)
                     && dir_create (bst, inode_get_inumber (dir_get_inode (path)))                     
                     && dir_add (path, temp1, bst));

  if (success)
  {
    struct inode *inode = NULL;
    if (dir_lookup (path, temp1, &inode))
	inode_set_isdir (inode, true);
  }

  dir_close (path);
  
  if (!success && bst != 0)
      free_map_release (bst, 1);

  return success;
}

/* read directory at position FD and stores file name
   in NAME */
static bool 
readdir_handler (int fd, char *name)
{
  if (fd < 3 || fd > 31)
      return false;
  
  struct thread *cur = thread_current ();
  struct dir *dir = cur->files[fd]->dir;
  if (dir == NULL)
 	return false;
  
  return dir_readdir (dir, name);
}

static bool 
isdir_handler (int fd)
{
  if (fd < 3 || fd > 31)
	return false;

  struct file_info *fi = thread_current ()->files[fd];

  return ((fi != NULL) && (fi->file == NULL) && (fi->dir != NULL));
}

static int 
inumber_handler (int fd)
{
  if (fd < 3 || fd > 31)
	return -1;

  struct file_info *fi = thread_current ()->files[fd];
  
  if (fi == NULL)
      return -1;

  if (fi->file != NULL)
      return inode_get_inumber (file_get_inode (fi->file));

  if (fi->dir != NULL)
      return inode_get_inumber (dir_get_inode (fi->dir));

  return -1;
}

/* Handler for MMAP system call */
static mapid_t
mmap_handler (int fd, void *addr)
{
  int filesize = filesize_handler (fd);
  if (fd < 3 || fd > 31)
    exit_handler (-1);
  if (!addr
      || ((uint8_t) addr % PGSIZE) != 0
      || filesize == 0)
    return MAP_FAILED;

  /* Check if the range of pages to be mapped overlaps any existing set */
  /* of mapped pages, including the stack or pages mapped at executable */
  /* load time */
  uint8_t *upage = addr;
  struct thread *t = thread_current ();
  while (upage < (addr + filesize))
  {
    uint32_t *page_table_entry = page_table_entry_lookup
                                 (upage, t->pagedir, false);

    if (page_table_entry)
    {
      if (supp_page_table_lookup (page_table_entry))
        return MAP_FAILED;
    }
    
    upage += PGSIZE;
  }

  /* Get an instance of the file referred to by 'fd' */
  /* by re-opening it. Thereafter, make all changes on the */
  /* fetched file pointer in for the memory mapped file */
  lock_acquire (&global_filesys_lock);
  struct file *file = file_reopen (t->files[fd]->file);
  lock_release (&global_filesys_lock);
  struct mmap_file_info *mfi = (struct mmap_file_info *)
                                malloc (sizeof (struct mmap_file_info));
  if (!mfi)
  {
    free (mfi);
    return MAP_FAILED;
  }

  uint32_t read_bytes = filesize;
  uint32_t zero_bytes = PGSIZE  - (filesize % PGSIZE);
  int count = read_bytes + zero_bytes;
  block_sector_t sector_num = byte_to_sector (file_get_inode (file), 0);
  upage = addr;

  if (sector_num == -1 || read_bytes == 0)
    return MAP_FAILED;

  while (count > 0)
  {
    if (read_bytes < PGSIZE)
    {
      allocate_page (FRAME_ON_DISK, READWRITE, MMAP_FILE, false,
		     upage, NULL, read_bytes, sector_num);
      count -= zero_bytes;
    }
    else
      allocate_page (FRAME_ON_DISK, READWRITE, MMAP_FILE, false,
		     upage, NULL, PGSIZE, sector_num);
    
    upage += PGSIZE;
    read_bytes -= PGSIZE;
    sector_num += (PGSIZE / BLOCK_SECTOR_SIZE);
    count -= PGSIZE;
  }

  t->mapid_count += 1;
  mfi->index = t->mapid_count;
  mfi->vaddr = addr;
  mfi->file = file;
  list_push_back (&t->mmap_file_list, &mfi->elem);

  return mfi->index;
}

/* Handler for MUNMAP system call */
static void
munmap_handler (mapid_t mapping)
{
  struct thread *t = thread_current ();
  
  if (mapping < 1 || mapping > t->mapid_count)
    return;
  
  int flag = 0;
  struct list_elem *e;
  struct mmap_file_info *mfi;
  for (e = list_begin (&t->mmap_file_list); e != list_end (&t->mmap_file_list);
       e = list_next (e))
  {
    mfi = list_entry (e, struct mmap_file_info, elem);
    if (mfi->index == mapping)
    {
      flag++;
      break;
    }
  }
  if (flag == 0)
    return;

  /* Write all the pages written to by the process back to the file */
  struct file *file = mfi->file;
  uint8_t *upage = mfi->vaddr;
  uint32_t read_bytes = file_length (file);
  uint32_t zero_bytes = PGSIZE - (read_bytes % PGSIZE);
  int count = read_bytes + zero_bytes;
  if (read_bytes == 0)
    return;

  while (count > 0)
  {
    if (read_bytes < PGSIZE)
    {
      lock_acquire (&global_filesys_lock);
      if (pagedir_is_dirty (t->pagedir, upage))
        file_write_at (file, upage, read_bytes, upage - mfi->vaddr);
      lock_release (&global_filesys_lock);
      count -= zero_bytes;
    }
    else
    {
      lock_acquire (&global_filesys_lock);
      if (pagedir_is_dirty (t->pagedir, upage))
        file_write_at (file, upage, PGSIZE, upage - mfi->vaddr);
      lock_release (&global_filesys_lock);
    }

    deallocate_page (upage);
    upage += PGSIZE;
    read_bytes -= PGSIZE;
    count -= PGSIZE;
  }
}

/* A user can pass a null pointer, a pointer to unmapped virtual memory, */
/* or a pointer to kernel virtual address space (above PHYS_BASE). */
/* All of these types of invalid pointers must be rejected without harm */
/* to the kernel or other running processes, by terminating the offending */
/* process and freeing its resources. */
/* Most of these objectives are met here, and the remaining in exception.c */
static bool 
checkforvalidaddr (void *esp)
{
  if (!is_user_vaddr (esp))
    return false;
  return true;
}

/* Informs the children that the parent T is terminated by setting  */
/* child->parent to NULL */
static void
inform_children(struct thread *t)
{
  struct list_elem *e;
  for (e = list_begin (&t->children); e != list_end (&t->children);
       e = list_next (e))
  {
    struct child *child = list_entry (e, struct child, elem);
    struct thread *child_t = get_thread (child->tid);
    if (child_t)
      child_t->parent = NULL;
  }
}
